package com.unitd.frame.mybatis.parser;

import com.unitd.frame.comm.utils.BeanUtils;
import com.unitd.frame.comm.utils.ListUtils;
import com.unitd.frame.comm.utils.MapUtils;
import com.unitd.frame.comm.utils.StringUtils;
import com.unitd.frame.spring.SpringInstanceFactory;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.w3c.dom.NodeList;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @desc Mybatis的mapper数据库字段与实体字段映射关系转换工具
 * @filename MybatisMapperParser.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2017/2/4
 */
public class MybatisMapperParser {

	private static final Logger log = LoggerFactory.getLogger(MybatisMapperParser.class);

	/** mapper文件所在路径 */
	private static String mapperLocations;
	/** MyBatis与数据库表、实体类之间的映射对象集合 */
	private static List<EntityInfo> entityInfos = new ArrayList<>();
	/** 缓存映射集合 */
	private static Map<String, Map<String, String>> caches = new HashMap<>();
	/** 实体类映射关联集合 */
	private static Map<String, List<MapResultItem>> entityRalateItems = new HashMap<>();
	/** 数据库表映射关联集合 */
	private static Map<String, List<MapResultItem>> tableRalateItems = new HashMap<>();
	/** mapper的命名空间映射关联集合 */
	private static Map<String, List<String>> namespaceRalateColumns = new HashMap<>();
	/** mapper映射的实体对象关联集合 */
	private static Map<String, EntityInfo> mapperRalateEntitys = new HashMap<>();

	/**
	 * @desc 设置/获取Mybatis的mapper文件的位置
	 * @param mapperLocations mapper文件路径
	 */
	public static void setMapperLocations(String mapperLocations) {
		MybatisMapperParser.mapperLocations = mapperLocations;
	}

	/**
	 * @desc 获取 MyBatis与数据库表、实体类之间的映射对象集合
	 * @return List
	 */
	public static List<EntityInfo> getEntityInfos() {
		// 执行解析方法,获取应用运行的mapper.xml文件对象信息
		doParse();
		return entityInfos;
	}

	/**
	 * @desc 根据mapper的具体名字获取相应的mapper.xml文件对象信息
	 * @param mapperName mapper名称
	 * @return EntityInfo
	 */
	public static EntityInfo getEntityInfoByMapper(String mapperName) {
		doParse();
		return mapperRalateEntitys.get(mapperName);
	}

	/**
	 * @desc 判断实体对象中是否存在指定的属性
	 * @param entityClass 需要判断的实体对象
	 * @param propName 指定的属性名
	 * @return boolean
	 */
	public static boolean entityHasProperty(Class<?> entityClass, String propName) {
		return propertyToColumnName(entityClass, propName) != null;
	}

	/**
	 * @desc 根据mapper映射的实体对象,获取与数据库中的指定字段对应的属性名
	 * @param entityClass mapper映射的实体对象
	 * @param columnName 数据库字段名
	 * @return String
	 */
	public static String columnToPropName(Class<?> entityClass, String columnName) {
		doParse();
		if (caches.containsKey(entityClass.getName())) {
			return caches.get(entityClass.getName()).get(columnName);
		}
		return null;
	}

	/**
	 * @desc 根据mapper映射的实体对象,获取与该属性对应的数据库字段名
	 * @param entityClass mapper映射的实体对象
	 * @param propName 属性名
	 * @return String
	 */
	private static String propertyToColumnName(Class<?> entityClass, String propName) {
		doParse();
		Map<String, String> map = caches.get(entityClass.getName());
		if (map != null) {
			for (String columnName : map.keySet()) {
				if (propName.equals(map.get(columnName))) {
					return columnName;
				}
			}
		}
		return null;
	}

	/**
	 * @desc 判断数据库表中是否存在指定的字段名(主要用于分库分表时使用)
	 * @param namespace 数据库表对应的命名空间
	 * @param columnName 指定字段名
	 * @return boolean
	 */
	public static boolean tableHasColumn(String namespace, String columnName) {
		List<String> list = namespaceRalateColumns.get(namespace);
		return list != null && list.contains(columnName.toLowerCase());
	}

	/**
	 * @desc 处理解析的总入库方法,读取spring加载的资源文件指定的mapper.xml文件,并根据情况获取或者设置相应的数据对象
	 * 增加synchronized同步锁,防止多个进程同时解析
	 */
	private static synchronized void doParse() {
		if (!caches.isEmpty()){
			return;
		}
		try {
			ResourceLoader resourceLoader = SpringInstanceFactory.getInstance(ResourceLoader.class);
			if (resourceLoader == null) {
				resourceLoader = new DefaultResourceLoader();
			}

			Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources(mapperLocations);
			for (Resource resource : resources) {
				log.info("开始解析mapper文件:" + resource);
				parseMapperFile(resource.getFilename(), resource.getInputStream());
			}
		} catch (Exception e) {
			log.error("解析mapper文件异常", e);
			throw new RuntimeException("解析mapper文件异常");
		}
	}

	/**
	 * @desc 根据mapper.xml文件具体路径,解析该mapper文件
	 * @param fileName mapper.xml文件具体路径
	 * @param inputStream mapper文件的流对象
	 */
	private static void parseMapperFile(String fileName, InputStream inputStream) throws Exception {

		// 将mapper.xml文件读取成一个xml对象
		XPathParser parser = new XPathParser(inputStream, true, null, new XMLMapperEntityResolver());

		// 读取mapper节点
		XNode evalNode = parser.evalNode("/mapper");

		// 读取mapper节点下的namespace节点
		String mapperClass = evalNode.getStringAttribute("namespace");

		// 读取namespace节点下的具体SQL语句或数据定义节点
		String entityClass;
		EntityInfo entityInfo = null;
		Map<String, String> includes = new HashMap<>(1);

		List<XNode> children = evalNode.getChildren();
		for (XNode xNode : children) {
			// sql节点,可以用来第能够以表名、公共的查询字段名、公共的where条件语句等
			if("sql".equalsIgnoreCase(xNode.getName())){
				includes.put(xNode.getStringAttribute("id"), xNode.getStringBody());
				continue;
			}
			// resultMap/BaseResultMap节点,用于定义sql执行的结果与实体对象的映射关系
			if (!"resultMap".equals(xNode.getName())) {
				continue;
			}
			if (!"BaseResultMap".equals(xNode.getStringAttribute("id"))) {
				continue;
			}

			entityClass = xNode.getStringAttribute("type");
			entityInfo = new EntityInfo(mapperClass, entityClass);

			if (entityInfo.getErrorMsg() != null) {
				log.warn("==================\n>>{},skip！！！！\n===============", entityInfo.getErrorMsg());
				continue;
			}
			entityInfos.add(entityInfo);
			mapperRalateEntitys.put(mapperClass, entityInfo);

			List<XNode> resultNodes = xNode.getChildren();
			for (XNode sonNode : resultNodes) {
				// 读取 resultMap/BaseResultMap节点子元素
				parseResultNode(entityInfo, sonNode);
			}
		}

		if (!BeanUtils.isNull(entityInfo) &&
				entityInfo.getErrorMsg() != null) {
			return;
		}

		for (XNode xNode : children) {
			if ("select|insert|update|delete".contains(xNode.getName().toLowerCase())) {
				String sql = parseSql(fileName, xNode, includes);
				entityInfo.addSql(xNode.getStringAttribute("id"), sql);
			}
		}

		// 关闭文件流
		inputStream.close();
	}

	/**
	 * @desc 读取 resultMap/BaseResultMap节点子元素
	 * @param entityInfo MyBatis与数据库表、实体类之间的映射对象
	 * @param node xml节点
	 */
	private static void parseResultNode(EntityInfo entityInfo, XNode node) {
		MapResultItem resultItem = new MapResultItem();
		resultItem.setEntityName(entityInfo.getEntityClass().getName());
		resultItem.setTableName(entityInfo.getTableName());
		resultItem.setColumnName(node.getStringAttribute("column"));
		resultItem.setPrimaryKey("id".equals(node.getName().toLowerCase()));
		resultItem.setPropertyName(node.getStringAttribute("property"));
		resultItem.setType(node.getStringAttribute("jdbcType"));

		// 先从缓存中取,加快自动映射的速度
		Map<String, String> resultRalate = caches.get(resultItem.getEntityName());
		if (MapUtils.isEmpty(resultRalate)) {
			resultRalate = new HashMap<>(1);
			caches.put(resultItem.getEntityName(), resultRalate);
		}
		resultRalate.put(resultItem.getColumnName(), resultItem.getPropertyName());

		// 取实体类映射对象中的属性
		List<MapResultItem> entityResultItems = entityRalateItems.get(resultItem.getEntityName());
		if (ListUtils.isNull(entityResultItems)) {
			entityResultItems = new ArrayList<>();
			entityRalateItems.put(resultItem.getEntityName(), entityResultItems);
		}
		entityResultItems.add(resultItem);

		// 取表映射对象中的属性
		List<MapResultItem> tableResultItems = tableRalateItems.get(resultItem.getEntityName());
		if (ListUtils.isNull(tableResultItems)) {
			tableResultItems = new ArrayList<>();
			tableRalateItems.put(resultItem.getTableName(), tableResultItems);
		}
		tableResultItems.add(resultItem);

		// 取mapper的命名空间
		List<String> namespaces = namespaceRalateColumns.get(entityInfo.getMapperClass().getName());
		if (ListUtils.isNull(namespaces)) {
			namespaces = new ArrayList<>();
			namespaceRalateColumns.put(entityInfo.getMapperClass().getName(), namespaces);
		}
		namespaces.add(resultItem.getColumnName());
	}

	/**
	 * @desc 解析具体的sql节点
	 * @param fileName 具体需要解析的mapper.xml文件
	 * @param node xml节点对象
	 * @param includeContents include节点包含的说明信息
	 * @return String
	 */
	private static String parseSql(String fileName, XNode node, Map<String, String> includeContents) {
		StringBuilder sql = new StringBuilder();

		NodeList children = node.getNode().getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			XNode child = node.newXNode(children.item(i));
			String data;
			if ("#text".equals(child.getName())) {
				data = child.getStringBody("");
			} else if ("include".equals(child.getName())) {
				String refId = child.getStringAttribute("refid");
				data = child.toString();
				if (includeContents.containsKey(refId)) {
					data = data.replaceAll("<\\s?include.*(" + refId + ").*>", includeContents.get(refId));
				} else {
					log.error(String.format(">>>>>Parse SQL from mapper[%s-%s] error,not found include key:%s", fileName, node.getStringAttribute("id"), refId));
				}
			} else {
				data = child.toString();
			}
			data = data.replaceAll("\\n+|\\t+", "");

			if(!StringUtils.isNull(data)) {
				sql.append(data).append("\t").append("\n");
			}
		}
		return sql.toString();
	}

	public static void main(String[] args) {
		String sql = "SELECT <include refid=\"base_fields\" /> dd > FROM users where type = #{type} ";
		System.out.println(sql.replaceAll("<\\s?include.*(base_fields).*>", "xxxx"));

		String mapperBaseDir = "G:/WorkPro/Unitd/BankPro_KF/bank/bank-server/server-param/param-service/src/main/resources/mybatis/mapper/param/SysRoleMapper.xml";
		URL resource = Thread.currentThread().getContextClassLoader().getResource(mapperBaseDir);
		if (resource != null) {

		}
	}
}